﻿/*
 * This file is part of CommunitySettlers.
 *
 * CommunitySettlers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * CommunitySettlers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with CommunitySettlers.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Christoph Husse
 * 
 * Also checkout our homepage: http://communitysettlers.codeplex.com/
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;

namespace SettlersEngine
{
    internal interface IMovableState
    {
        CyclePoint Position { get; }
        Boolean IsMoving { get; }

        void Stop();
    }


    internal class MovableManager<TPathNode>
        where TPathNode : NativeEngine.IAStarNode
    {
        private readonly CooperativeAStar<TPathNode> m_AStar;
        private readonly LinkedList<MovableState> m_Movables = new LinkedList<MovableState>();
        public long CurrentCycle { get { return m_AStar.CurrentCycle; } }
        public long CycleResolution { get { return m_AStar.CycleResolution; } }

        /// <summary>
        /// Movable will carry a MovableState during whole lifetime. 
        /// If they are idle, pathplanning is still done. Normally they will stay at their
        /// place but if any other movable crosses their idle spot, idle settlers are
        /// automatically replanned to walk out of way. This behavior is done entirely in
        /// cooperative a-star and requires no extra work, except idle planning!
        /// </summary>
        private class MovableState : IMovableState, IPathReservation
        {
            public int CurrentNodeIndex { get; set; }
            public int MovableType { get; set; }
            public long ReplanTime { get; set; }
            public long StartTime { get; set; }
            public int CycleSpeed { get; set; }
            public IMovableObject Movable { get; set; }
            public System.Drawing.Point PathSource { get; set; }
            public System.Drawing.Point PathTarget { get; set; }
            public List<PathNode> Path { get; set; }
            public MovableManager<TPathNode> Parent { get; set; }
            public LinkedListNode<MovableState> ListNode { get; set; }
            public Boolean IsInvalid
            {
                get
                {
                    if (Path != null)
                        return false;

                    return PathSource != PathTarget;
                }
            }
            public CyclePoint Position { get; set;  }
            public Boolean IsMoving { 
                get { return PathSource != PathTarget; } }

            public void Invalidate()
            {
                if (Path == null)
                    return;

                Parent.m_AStar.RemoveReservation(Path.Select(e => e.Key));

                Path = null;
            }

            public void Stop()
            {
                PathTarget = PathSource;

                Invalidate();
            }

            public void Dispose()
            {
                Stop();
            }
        }

        private class PathNode 
        {
            public CooperativeAStar<TPathNode>.PathNodeKey Key { get; set; }
            public Direction Direction { get; set; }
            public Boolean IsIdle { get; set; }
        }

        public MovableManager(TPathNode[,] inSearchSpace, Int64 inCurrentCycle, Int64 inCycleResolution)
        {
            m_AStar = new CooperativeAStar<TPathNode>(inSearchSpace, inCurrentCycle, inCycleResolution);
        }

        public void AddOneCycle()
        {
            m_AStar.AddOneCycle();

            Boolean canAcquirePath = (CurrentCycle % CycleResolution) == 0;
            
            foreach (var state in m_Movables)
            {
                if (state.IsInvalid)
                {
                    if (!canAcquirePath)
                        continue;

                    // acquire new path
                    IEnumerable<CooperativeAStar<TPathNode>.PathNodeKey> nativePath = m_AStar.SearchRaw(
                        1, state.PathSource, 
                        state.Movable.GridPosition, 
                        state.PathTarget, 
                        0);
                    PathNode lastNode = null;
                    Direction lastDirection = state.Movable.Direction;

                    state.Path = (from e in nativePath
                                 select new PathNode()
                                 {
                                     Key = e,
                                 }).ToList();

                    for (int i = 0, count = state.Path.Count; i < count; i++)
                    {
                        PathNode node = state.Path[i];
                        PathNode nextNode;

                        if (i + 1 < count - 1)
                        {
                            Direction dir;
                            nextNode = state.Path[i + 1];

                            if (GetWalkingDirection(new Point(node.Key.X, node.Key.Y), new Point(nextNode.Key.X, nextNode.Key.Y), out dir))
                            {
                                lastDirection = node.Direction = dir;
                            }
                            else
                            {
                                node.Direction = lastDirection;
                                node.IsIdle = true;
                            }
                        }
                        else
                            node.Direction = lastDirection;

                        lastNode = node;
                    }

                    state.ReplanTime = lastNode.Key.Time;
                    state.CurrentNodeIndex = 0;
                    state.StartTime = state.Path.First().Key.Time;

                    // get first two path nodes
                    PathNode first = state.Path[0];
                    int xDiff = (int)(state.Position.XCycles - first.Key.X * RuntimeMap.GRIDPOS_TO_CYCLES);
                    int yDiff = (int)(state.Position.YCycles - first.Key.Y * RuntimeMap.GRIDPOS_TO_CYCLES);

                    Debug.Assert(xDiff == 0);
                    Debug.Assert(yDiff >= 0);
                }
                else
                {
                    if (state.StartTime > CurrentCycle)
                        continue;

                    // is planning time over?
                    if (state.ReplanTime <= CurrentCycle)
                    {
                        state.Invalidate();
                    }
                    else if(state.IsMoving)
                    {
                        // process path progress
                        var path = state.Path;
                        PathNode currentNode = path[state.CurrentNodeIndex];

                        if (!currentNode.IsIdle)
                        {
                            // check for grid position change
                            CyclePoint cyclePos = state.Position;
                            Point prevGrid = state.Position.ToPoint();
                            Point dirVec = GetDirectionVector(currentNode.Direction);
                            Point currentGrid;

                            cyclePos = cyclePos.AddCycleVector(dirVec);

                            currentGrid = cyclePos.ToPoint();

                            if (state.Movable.Direction != currentNode.Direction)
                                state.Movable.Direction = currentNode.Direction;

                            state.Movable.Position = state.Position = cyclePos;

                            if (currentGrid != prevGrid)
                            {
                                PathNode nextNode = path[state.CurrentNodeIndex + 1];

                                Debug.Assert(nextNode.Key.Time == CurrentCycle,
                                    "Movement cycle time is asynchronous to path node time.");

                                state.CurrentNodeIndex++;
                            }
                        }
                        else
                        {
                            // wait for block switch
                            if (state.Path.Count > state.CurrentNodeIndex + 1)
                            {
                                PathNode nextNode = path[state.CurrentNodeIndex + 1];

                                if (nextNode.Key.Time <= CurrentCycle)
                                    state.CurrentNodeIndex++;
                            }
                        }
                    }
                }
            }
        }

        internal Boolean GetWalkingDirection(Point inFromPos, Point inToPos, out Direction outDirection)
        {
            int diffX = inToPos.X - inFromPos.X;
            int diffY = inToPos.Y - inFromPos.Y;

            outDirection = Direction._000;

            if (diffX == 0)
            {
                if (diffY == -1) outDirection = Direction._000;
                else if (diffY == 1) outDirection = Direction._180;
                else return false;
            }
            else if (diffX == 1)
            {
                if (diffY == -1) outDirection = Direction._045;
                else if (diffY == 0) outDirection = Direction._090;
                else if (diffY == 1) outDirection = Direction._135;
                else return false;
            }
            else if (diffX == -1)
            {
                if (diffY == -1) outDirection = Direction._315;
                else if (diffY == 0) outDirection = Direction._270;
                else if (diffY == 1) outDirection = Direction._225;
                else return false;
            }
            else return false;

            return true;
        }

        public Point GetDirectionVector(Direction inDirection)
        {
            Point result;

            switch (inDirection)
            {
                case Direction._000: result = new Point(0, -1); break;
                case Direction._045: result = new Point(1, -1); break;
                case Direction._090: result = new Point(1, 0); break;
                case Direction._135: result = new Point(1, 1); break;
                case Direction._180: result = new Point(0, 1); break;
                case Direction._225: result = new Point(-1, 1); break;
                case Direction._270: result = new Point(-1, 0); break;
                case Direction._315: result = new Point(-1, -1); break;
                default:
                    throw new ArgumentException();
            }

            return result;
        }

        public void Register(IMovableObject inMovable)
        {
            if (inMovable.MovableState != null)
                throw new ApplicationException("Movable is already registered.");

            MovableState state = new MovableState()
            { 
                PathSource = inMovable.GridPosition,
                PathTarget = inMovable.GridPosition,
                Parent = this,
                Movable = inMovable,
                MovableType = 0,
                Position = inMovable.Position,
            };

            state.ListNode = m_Movables.AddLast(state);

            inMovable.MovableState = state;
        }

        public void Unregister(IMovableObject inMovable)
        {
            if (inMovable.MovableState == null)
                return;

            MovableState state = (MovableState)inMovable.MovableState;

            m_Movables.Remove(state.ListNode);

            inMovable.MovableState = null;
        }

        public void SetPath(IMovableObject inMovable, System.Drawing.Point inTarget)
        {
            MovableState state = (MovableState)inMovable.MovableState;

            if (state.PathTarget == inTarget)
                return;

            // update path information
            state.PathTarget = inTarget;
            state.PathSource = inMovable.GridPosition;

            state.Invalidate();
        }
    }
}
